from __future__ import annotations

import re
import uuid
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, Iterable, Iterator, List, Optional, Sequence

from agent_society.memory.store import MemoryDocument

SENT_START = "==== as:event v1"
SENT_END = "==== end ====\n"


@dataclass(slots=True)
class Event:
    id: str
    type: str
    owner: str
    actor: str
    created_at: str
    headers: Dict[str, str]
    content: str

    def to_dict(self) -> Dict[str, str]:
        return {
            "id": self.id,
            "type": self.type,
            "owner": self.owner,
            "actor": self.actor,
            "created_at": self.created_at,
            "headers": dict(self.headers),
            "content": self.content,
        }


def now_iso() -> str:
    return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")


def new_event_id(ts: Optional[str] = None) -> str:
    timestamp = ts or now_iso()
    nonce = uuid.uuid4().hex[:6]
    return f"{timestamp}-{nonce}"

def format_block(event: Event) -> str:
    head_tokens = [
        f"type={event.type}",
        f"id={event.id}",
        f"owner={event.owner}",
        f"actor={event.actor}",
    ]
    header_line = f"{SENT_START} " + " ".join(head_tokens) + " ====\n"
    meta_lines = [f"created_at: {event.created_at}"]
    for key in sorted(event.headers.keys()):
        meta_lines.append(f"{key}: {event.headers[key]}")
    meta_block = "\n".join(meta_lines) + "\n"
    body = event.content.rstrip("\n") + "\n"
    return header_line + meta_block + "---\n" + body + "---\n" + SENT_END


BLOCK_RE = re.compile(
    r"^==== as:event v1 (?P<meta>[^\n]+) ====\n"  # header line
    r"(?P<headers>.*?)(?:^---\n)"  # headers until delimiter
    r"(?P<body>.*?)(?:^---\n)"  # body until delimiter
    r"==== end ====\n?",
    re.S | re.M,
)


def parse_blocks(text: str) -> Iterator[Event]:
    for match in BLOCK_RE.finditer(text):
        meta_str = match.group("meta")
        header_tokens = {}
        for token in meta_str.split():
            if "=" in token:
                key, value = token.split("=", 1)
                header_tokens[key] = value

        headers_blob = match.group("headers")
        headers: Dict[str, str] = {}
        created_at = ""
        for line in headers_blob.splitlines():
            if ":" not in line:
                continue
            key, value = line.split(":", 1)
            key = key.strip()
            value = value.strip()
            if key == "created_at":
                created_at = value
            else:
                headers[key] = value

        body = match.group("body").rstrip("\n")
        yield Event(
            id=header_tokens.get("id", ""),
            type=header_tokens.get("type", ""),
            owner=header_tokens.get("owner", ""),
            actor=header_tokens.get("actor", ""),
            created_at=created_at or now_iso(),
            headers=headers,
            content=body,
        )


def append_event(
    base_dir: Path,
    owner: str,
    actor: str,
    ev_type: str,
    content: str,
    headers: Optional[Dict[str, str]] = None,
    *,
    created_at: Optional[str] = None,
    event_id: Optional[str] = None,
) -> Event:
    event = Event(
        id=event_id or new_event_id(created_at),
        type=ev_type,
        owner=owner,
        actor=actor,
        created_at=created_at or now_iso(),
        headers=headers or {},
        content=content,
    )
    owner_dir = base_dir / owner / actor
    owner_dir.mkdir(parents=True, exist_ok=True)
    file_path = owner_dir / "activity.log"
    block = format_block(event)
    existing = file_path.read_text(encoding="utf-8") if file_path.exists() else ""
    if existing and not existing.endswith("\n"):
        existing += "\n"
    new_text = existing + block
    file_path.write_text(new_text, encoding="utf-8")
    return event


def read_events(base_dir: Path, owner: str, actors: Optional[Iterable[str]] = None) -> List[Event]:
    events: List[Event] = []
    owner_dir = base_dir / owner
    if not owner_dir.exists():
        return events
    candidates = list(owner_dir.iterdir())
    for actor_dir in sorted(candidates):
        if not actor_dir.is_dir():
            continue
        actor = actor_dir.name
        if actors is not None and actor not in actors:
            continue
        file_path = actor_dir / "activity.log"
        if not file_path.exists():
            continue
        try:
            payload = file_path.read_text(encoding="utf-8")
        except Exception:
            continue
        events.extend(parse_blocks(payload))
    return events


def events_to_documents(events: Iterable[Event]) -> List[MemoryDocument]:
    docs: List[MemoryDocument] = []
    for ev in events:
        summary = ev.content
        # Provide meaningful placeholder for empty content to avoid TF-IDF errors
        if not summary or summary.strip() == "":
            # Create a descriptive placeholder based on event type
            summary = f"Event: {ev.type} by {ev.actor} for {ev.owner}"
            if ev.headers:
                # Add header info for context
                header_info = ", ".join(f"{k}={v}" for k, v in ev.headers.items() if v)
                if header_info:
                    summary += f" ({header_info})"
        tags = {
            "event_type": ev.type,
            "actor": ev.actor,
            "owner": ev.owner,
        }
        docs.append(MemoryDocument(content=summary, tags=tags))
    return docs
